package com.ruoyi.kq.utils;

import com.alibaba.fastjson.JSONObject;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import lombok.extern.slf4j.Slf4j;
import net.sourceforge.tess4j.ITesseract;
import net.sourceforge.tess4j.Tesseract;
import net.sourceforge.tess4j.TesseractException;
import org.apache.commons.imaging.Imaging;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.opencv.core.Mat;
import org.opencv.core.MatOfPoint;
import org.opencv.core.Rect;
import org.opencv.core.Size;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 图像文字提取工具类
 * 依赖：
 * - Tess4J (Tesseract for Java)
 * - OpenCV
 * - Apache Commons Imaging
 */
@Slf4j
public class ImageTextExtractorUtil {

    private static final String DEFAULT_LANGUAGE = "chi_sim+eng"; // 默认支持中文简体和英文
    // 本地模式
    private static String tessdataPath = "D:\\tool\\Tesseract-OCR\\tessdata\\tessdata-4.1.0";
    //private static String tessdataPath = "/usr/share/tesseract/tessdata";
    private static boolean isOpenCVLoaded = false;

    static {
        try {
            // 加载OpenCV库
            nu.pattern.OpenCV.loadLocally();
            isOpenCVLoaded = true;
            log.info("OpenCV库加载成功");
        } catch (Exception e) {
            log.error("OpenCV库加载失败: {}", e.getMessage());
        }
    }

    /**
     * 从图像文件中提取文字
     *
     * @param imagePath 图像文件路径
     * @return 提取的文字
     */
    public static String extractText(String imagePath) {
        return extractText(imagePath, DEFAULT_LANGUAGE, false);
    }

    /**
     * 从图像文件中提取文字
     *
     * @param imagePath  图像文件路径
     * @param language   识别语言，例如"chi_sim"(中文简体),"eng"(英文)
     * @param preprocess 是否进行图像预处理
     * @return 提取的文字
     */
    public static String extractText(String imagePath, String language, boolean preprocess) {
        File imageFile = new File(imagePath);
        if (!imageFile.exists()) {
            log.error("图像文件不存在: {}", imagePath);
            return "";
        }

        try {
            // 使用Apache Commons Imaging读取图像
            BufferedImage bufferedImage = Imaging.getBufferedImage(imageFile);

            // 如果需要预处理，先处理图像
            if (preprocess && isOpenCVLoaded) {
                String processedImagePath = preprocessImage(imagePath);
                if (processedImagePath != null) {
                    imagePath = processedImagePath;
                    imageFile = new File(imagePath);
                    bufferedImage = Imaging.getBufferedImage(imageFile);
                }
            }

            // 创建Tesseract实例
            ITesseract tesseract = new Tesseract();
            tesseract.setDatapath(tessdataPath);
            tesseract.setLanguage(language);

            // 设置OCR引擎模式 (0=传统Tesseract，1=神经网络LSTM，2=两者结合，3=默认)
            tesseract.setOcrEngineMode(1);

            // 设置页面分割模式 (6=假设为单个统一块文本)
            tesseract.setPageSegMode(6);

            // 执行OCR
            String result = tesseract.doOCR(bufferedImage);

            // 清理临时文件
            if (preprocess && !imagePath.equals(imageFile.getPath())) {
                try {
                    Files.deleteIfExists(Paths.get(imagePath));
                } catch (IOException e) {
                    log.warn("无法删除临时文件: {}", imagePath);
                }
            }

            return result.trim();
        } catch (TesseractException | IOException e) {
            log.error("OCR识别失败: {}", e.getMessage());
            return "";
        } catch (Exception e) {
            log.error("OCR识别其他原因: {}", e.getMessage());
            return "";
        }
    }

    /**
     * 从图像中提取指定区域的文字
     *
     * @param imagePath 图像文件路径
     * @param x         区域左上角x坐标
     * @param y         区域左上角y坐标
     * @param width     区域宽度
     * @param height    区域高度
     * @return 提取的文字
     */
    public static String extractTextFromRegion(String imagePath, int x, int y, int width, int height) {
        try {
            BufferedImage image = ImageIO.read(new File(imagePath));
            if (x < 0 || y < 0 || width <= 0 || height <= 0 || x + width > image.getWidth() || y + height > image.getHeight()) {
                log.error("指定区域超出图像范围");
                return "";
            }

            // 裁剪图像
            BufferedImage regionImage = image.getSubimage(x, y, width, height);

            // 保存临时文件
            Path tempFile = Files.createTempFile("region_", ".png");
            ImageIO.write(regionImage, "png", tempFile.toFile());

            // 识别文字
            String result = extractText(tempFile.toString());

            // 删除临时文件
            Files.deleteIfExists(tempFile);

            return result;
        } catch (IOException e) {
            log.error("处理图像区域失败: {}", e.getMessage());
            return "";
        }
    }

    /**
     * 检测并提取图像中的所有文本区域
     *
     * @param imagePath 图像文件路径
     * @return 文本区域列表及其内容
     */
    public static List<TextRegion> detectAndExtractTextRegions(String imagePath) {
        List<TextRegion> results = new ArrayList<>();

        if (!isOpenCVLoaded) {
            log.error("OpenCV未加载，无法检测文本区域");
            return results;
        }

        // 读取图像
        Mat image = Imgcodecs.imread(imagePath);
        if (image.empty()) {
            log.error("无法读取图像: {}", imagePath);
            return results;
        }

        try {
            // 转换为灰度图
            Mat gray = new Mat();
            Imgproc.cvtColor(image, gray, Imgproc.COLOR_BGR2GRAY);

            // 二值化
            Mat binary = new Mat();
            Imgproc.threshold(gray, binary, 0, 255, Imgproc.THRESH_BINARY_INV + Imgproc.THRESH_OTSU);

            // 膨胀操作，连接字符
            Mat dilated = new Mat();
            Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(5, 5));
            Imgproc.dilate(binary, dilated, kernel);

            // 查找轮廓
            List<MatOfPoint> contours = new ArrayList<>();
            Mat hierarchy = new Mat();
            Imgproc.findContours(dilated, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);

            // 处理每个轮廓
            for (MatOfPoint contour : contours) {
                Rect rect = Imgproc.boundingRect(contour);

                // 过滤掉太小的区域
                if (rect.width < 30 || rect.height < 10) {
                    continue;
                }

                // 提取区域文字
                String text = extractTextFromRegion(imagePath, rect.x, rect.y, rect.width, rect.height);
                if (!text.isEmpty()) {
                    TextRegion region = new TextRegion(rect.x, rect.y, rect.width, rect.height, text);
                    results.add(region);
                }
            }

            return results;
        } finally {
            // 释放资源
            image.release();
        }
    }

    /**
     * 图像预处理，提高OCR准确率
     *
     * @param imagePath 图像文件路径
     * @return 处理后的图像文件路径
     */
    private static String preprocessImage(String imagePath) {
        if (!isOpenCVLoaded) {
            return null;
        }

        try {
            // 读取图像
            Mat image = Imgcodecs.imread(imagePath);
            if (image.empty()) {
                log.error("无法读取图像: {}", imagePath);
                return null;
            }

            // 转换为灰度图
            Mat gray = new Mat();
            Imgproc.cvtColor(image, gray, Imgproc.COLOR_BGR2GRAY);

            // 应用高斯模糊去噪
            Mat blurred = new Mat();
            Imgproc.GaussianBlur(gray, blurred, new Size(5, 5), 0);

            // 自适应阈值二值化
            Mat binary = new Mat();
            Imgproc.adaptiveThreshold(blurred, binary, 255, Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C, Imgproc.THRESH_BINARY, 11, 2);

            // 创建临时文件
            Path tempFile = Files.createTempFile("processed_", ".png");
            String outputPath = tempFile.toString();

            // 保存处理后的图像
            Imgcodecs.imwrite(outputPath, binary);

            // 释放资源
            image.release();
            gray.release();
            blurred.release();
            binary.release();

            return outputPath;
        } catch (Exception e) {
            log.error("图像预处理失败: {}", e.getMessage());
            return null;
        }
    }

    /**
     * 文本区域类，存储区域位置和识别的文本
     */
    public static class TextRegion {
        private int x;
        private int y;
        private int width;
        private int height;
        private String text;

        public TextRegion(int x, int y, int width, int height, String text) {
            this.x = x;
            this.y = y;
            this.width = width;
            this.height = height;
            this.text = text;
        }

        public int getX() {
            return x;
        }

        public int getY() {
            return y;
        }

        public int getWidth() {
            return width;
        }

        public int getHeight() {
            return height;
        }

        public String getText() {
            return text;
        }

        @Override
        public String toString() {
            return String.format("TextRegion[x=%d, y=%d, width=%d, height=%d, text='%s']", x, y, width, height, text);
        }
    }

    /**
     * 从身份证照片中提取信息并返回 JSON 格式
     *
     * @param imagePath 身份证照片路径
     * @return 包含身份证信息的 JSON 对象
     */
    public static JSONObject extractIdCardInfo(String imagePath) {
        JSONObject idCardInfo = new JSONObject();
        String extractedText = extractText(imagePath, "chi_sim", true);

        // 假设身份证信息包含姓名、性别、民族、出生日期、地址、身份证号码
        // 使用正则表达式或字符串处理来提取这些信息
        // 这里只是一个简单的示例，具体的提取逻辑需要根据实际的身份证格式来实现

        // 示例：假设提取的文本格式为：
        // 姓名: 张三
        // 性别: 男
        // 民族: 汉
        // 出生日期: 1990年01月01日
        // 地址: 北京市海淀区
        // 身份证号码: 11010119900101001X

        String[] lines = extractedText.split("\n");
        for (String line : lines) {
            if (line.contains("姓名")) {
                idCardInfo.put("name", line.split(":")[1].trim());
            } else if (line.contains("性别")) {
                idCardInfo.put("gender", line.split(":")[1].trim());
            } else if (line.contains("民族")) {
                idCardInfo.put("ethnicity", line.split(":")[1].trim());
            } else if (line.contains("出生日期")) {
                idCardInfo.put("birthdate", line.split(":")[1].trim());
            } else if (line.contains("地址")) {
                idCardInfo.put("address", line.split(":")[1].trim());
            } else if (line.contains("身份证号码")) {
                idCardInfo.put("idNumber", line.split(":")[1].trim());
            }
        }

        return idCardInfo;
    }

    public static void insertQRCode(String pdfPath, String outputPdfPath, String url) throws IOException, WriterException {
        // 生成二维码
        BufferedImage qrCodeImage = generateQRCodeImage(url);

        // 读取 PDF 文件
        try (PDDocument document = PDDocument.load(new File(pdfPath))) {
            PDPage page = document.getPage(0);

            // 创建二维码图像对象
            PDImageXObject pdImage = LosslessFactory.createFromImage(document, qrCodeImage);

            // 在 PDF 上绘制二维码
            try (PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true, true)) {
                float x = page.getMediaBox().getWidth() - 100; // 右上角 x 坐标
                float y = page.getMediaBox().getHeight() - 100; // 右上角 y 坐标
                contentStream.drawImage(pdImage, x, y, 100, 100); // 绘制二维码
            }

            // 保存修改后的 PDF 文件
            document.save(outputPdfPath);
        }
    }

    public static void insertQRCodeWithStream(OutputStream outputStream, String url) throws IOException, WriterException {
        // 生成二维码
        BufferedImage qrCodeImage = generateQRCodeImage(url);

        // 创建新的 PDF 文档
        try (PDDocument document = new PDDocument()) {
            PDPage page = new PDPage();
            document.addPage(page);

            // 创建二维码图像对象
            PDImageXObject pdImage = LosslessFactory.createFromImage(document, qrCodeImage);

            // 在 PDF 上绘制二维码
            try (PDPageContentStream contentStream = new PDPageContentStream(document, page)) {
                float x = page.getMediaBox().getWidth() - 100; // 右上角 x 坐标
                float y = page.getMediaBox().getHeight() - 100; // 右上角 y 坐标
                contentStream.drawImage(pdImage, x, y, 100, 100); // 绘制二维码
            }

            // 通过 OutputStream 输出 PDF
            document.save(outputStream);
        }
    }

    private static BufferedImage generateQRCodeImage(String url) throws WriterException {
        QRCodeWriter qrCodeWriter = new QRCodeWriter();
        Map<EncodeHintType, Object> hints = new HashMap<>();
        hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
        BitMatrix bitMatrix = qrCodeWriter.encode(url, BarcodeFormat.QR_CODE, 100, 100, hints);

        BufferedImage image = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB);
        for (int x = 0; x < 100; x++) {
            for (int y = 0; y < 100; y++) {
                image.setRGB(x, y, bitMatrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF);
            }
        }
        return image;
    }
}